#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <argtable2.h>
#include <stdbool.h>
#include <string.h>
#include "cJSON.h"
#include <sys/stat.h>
#include <sys/time.h>

#include <SDL.h>
#include <SDL_ttf.h>
#include <SDL_image.h>
#include <SDL_rotozoom.h>

#include "renderer.h"
#include "poller.h"

#include "pollers/sabnzb.h"
#include "pollers/imap.h"
#include "pollers/rss.h"
#include "pollers/jenkins.h"

#include "renderers/clock.h"
#include "renderers/mail.h"
#include "renderers/download.h"
#include "renderers/build.h"
#include "renderers/news.h"

#include "infowall.h"

static const poller_t* pollers[] = { &sabnzbpoller, &imappoller, &rsspoller, &jenkinspoller };
static const renderer_t* renderers[] = { &downloadrenderer, &newsrenderer, &mailrenderer, &clockrenderer, &buildrender };

typedef enum {
	RUNNING, EXIT
} state_t;

static bool windowed = false;

static SDL_Surface* screen;
static SDL_Surface* spinner;
static state_t state = RUNNING;
static GThread* pthread;
static bool polling = false;

static unsigned visiblerenders = 0;

/* Subtract the `struct timeval' values X and Y,
 storing the result in RESULT.
 Return 1 if the difference is negative, otherwise 0.  */

static int timeval_subtract(result, x, y)
	struct timeval *result, *x, *y; {
	/* Perform the carry for the later subtraction by updating y. */
	if (x->tv_usec < y->tv_usec) {
		int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
		y->tv_usec -= 1000000 * nsec;
		y->tv_sec += nsec;
	}
	if (x->tv_usec - y->tv_usec > 1000000) {
		int nsec = (x->tv_usec - y->tv_usec) / 1000000;
		y->tv_usec += 1000000 * nsec;
		y->tv_sec -= nsec;
	}

	/* Compute the time remaining to wait.
	 tv_usec is certainly positive. */
	result->tv_sec = x->tv_sec - y->tv_sec;
	result->tv_usec = x->tv_usec - y->tv_usec;

	/* Return 1 if result is negative. */
	return x->tv_sec < y->tv_sec;
}

static void parseargs(int argc, char* argv[]) {

	struct arg_lit *window = arg_lit0("w", "window", "");
	struct arg_end *end = arg_end(20);

	void* argtable[] = { window, end };

	arg_parse(argc, argv, argtable);

	if (window->count > 0) {
		printf("using window mode\n");
		windowed = true;
	}

	arg_free(argtable);

}

static void parseconfig() {
	uid_t uid = getuid();
	struct passwd* pwd = getpwuid(uid);

	char* filepath = malloc(1024);
	sprintf(filepath, "%s/.infowallrc", pwd->pw_dir);
	printf("looking for config in %s\n", filepath);

	FILE* configfile = fopen(filepath, "r");
	if (configfile == NULL ) {
		printf("unable to open config!\n");
	}
	else {
		printf("parsing config...\n");

		struct stat cs;
		fstat(configfile->_fileno, &cs);

		char* config = malloc(cs.st_size);
		fread(config, 1, cs.st_size, configfile);

		cJSON* root = cJSON_Parse(config);
		printf("config is %s\n", cJSON_Print(root));

		if (root == NULL ) {
			printf("failed to parse config\n");
		}
		else {

			cJSON* configpollers = cJSON_GetObjectItem(root, "pollers");
			if (pollers == NULL ) {
				printf("you need to configure some pollers\n");
			}
			else {

				cJSON* configpoller = configpollers->child;
				while (configpoller != NULL ) {
					printf("parsing config for %s\n", cJSON_GetObjectItem(configpoller, "poller")->valuestring);

					for (int poller = 0; poller < SIZEOFARRAY(pollers); poller++) {
						char* pollertag = cJSON_GetObjectItem(configpoller, "poller")->valuestring;
						if (pollertag == NULL ) {
							printf("poller block doesnt have tag!!\n");
						}
						else {
							printf("searching for poller for tag %s\n", pollertag);
							if (pollers[poller]->configtag != NULL
									&& strcmp(pollertag, pollers[poller]->configtag) == 0) {
								printf("sending config lump to poller\n");
								if (pollers[poller]->parseconfig != NULL ) {
									pollers[poller]->parseconfig(configpoller);
								}
								break;
							}
						}
					}

					configpoller = configpoller->next;
				}
			}

		}

		cJSON_Delete(root);
		free(config);
	}

	free(filepath);

}

static gpointer pollerthread(gpointer data) {

	printf("Poller thread started!\n");
	// do polling

	for (int i = 0; i < SIZEOFARRAY(pollers); i++) {
		if (pollers[i]->create != NULL ) {
			pollers[i]->create();
		}
	}

	while (state != EXIT) {
		sleep(10);
		polling = true;
		for (int poller = 0; poller < SIZEOFARRAY(pollers); poller++) {
			if (pollers[poller]->poll != NULL ) {
				pollers[poller]->poll();
			}
		}
		polling = false;
	}

	disposepollers();

	return NULL ;
}

static void initpollers() {

	pthread = g_thread_create(pollerthread, NULL, true, NULL );

}

static void initrenderers(fonts_t* fonts) {

	for (int i = 0; i < SIZEOFARRAY(renderers); i++) {
		if (renderers[i]->create != NULL ) {
			renderers[i]->create(fonts);
		}
	}
}

static void disposepollers() {
	for (int i = 0; i < SIZEOFARRAY(pollers); i++) {
		if (pollers[i]->dispose != NULL ) {
			pollers[i]->dispose();
		}
	}
}

static void disposerenderers() {
	for (int i = 0; i < SIZEOFARRAY(renderers); i++) {
		if (renderers[i]->dispose != NULL ) {
			renderers[i]->dispose();
		}
	}
}

static void render(SDL_Surface* renderersurface, unsigned delta, unsigned long elapsed) {
	SDL_Rect rendererrect;
	rendererrect.w = screen->w;
	rendererrect.x = 0;
	rendererrect.h = RENDERER_HEIGHT;

	int activerenderers = 0;

	for (int renderer = 0; renderer < visiblerenders && renderer < (sizeof(renderers) / sizeof(renderers[0]));
			renderer++) {

		renderer_t* r = renderers[renderer];
		if (r->update != NULL )
			r->update();

		if ((polling && renderers[renderer]->shouldrender != NULL && renderers[renderer]->shouldrender())
				|| (renderers[renderer]->needstorender != NULL && renderers[renderer]->needstorender())) {
			rendererrect.y = RENDERER_HEIGHT * activerenderers;
			activerenderers++;

			if (activerenderers == 1)
				SDL_FillRect(screen, NULL, 0x0000);

			if (renderers[renderer]->render != NULL ) {
				renderers[renderer]->render(renderersurface, delta);
				SDL_BlitSurface(renderersurface, NULL, screen, &rendererrect);
			}
			else {
				printf("renderer %d is null :(\n", renderer);
			}
		}
	}

	if (polling) {
		SDL_Rect dst;
		dst.x = 20;
		dst.y = 20;
		dst.w = spinner->w;
		dst.h = spinner->h;

		SDL_Surface* roted = rotozoomSurface(spinner, (elapsed / 1500) % 360, 1, SMOOTHING_ON);
		SDL_Rect src;
		src.x = (roted->w - dst.w) / 2;
		src.y = (roted->h - dst.h) / 2;
		src.w = dst.w;
		src.h = dst.h;
		SDL_BlitSurface(roted, &src, screen, &dst);
		SDL_FreeSurface(roted);
	}

	SDL_Flip(screen);

}

int main(int argc, char* argv[]) {

	g_thread_init(NULL ); // Init threads

	struct timeval lastime, thistime, difference;

	spinner = IMG_Load("./assets/spinner.png");
	if (spinner == NULL ) {
		printf("Couldnt load spinner\n");
		return 1;
	}

	parseargs(argc, argv);
	parseconfig();

	initpollers();

	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);

	Uint32 flags = SDL_SWSURFACE;
	if (!windowed) {
		flags |= SDL_FULLSCREEN;
		SDL_ShowCursor(0);
	}

	screen = SDL_SetVideoMode(WINDOWWIDTH, WINDOWHEIGHT, 16, flags);

	if (screen == NULL ) {
		printf("SDL init failed\n");
		return 1;
	}

	visiblerenders = ((screen->h - (screen->h % RENDERER_HEIGHT)) / RENDERER_HEIGHT);

	TTF_Init();
	TTF_Font* font = TTF_OpenFont("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf", 32);
	fonts_t fonts = { font, font, font };
	initrenderers(&fonts);

	SDL_Surface* renderersurface = SDL_CreateRGBSurface(SDL_SWSURFACE, screen->w, RENDERER_HEIGHT, 16, 0, 0, 0, 0);
	SDL_SetAlpha(renderersurface, SDL_SRCALPHA | SDL_RLEACCEL, 0xAA);

	// main loop

	gettimeofday(&lastime, NULL );

	while (state != EXIT) {

		static unsigned long elapsed = 0;
		unsigned delta = 0;

		gettimeofday(&thistime, NULL );
		if (timeval_subtract(&difference, &thistime, &lastime) == 0) {
			memcpy(&lastime, &thistime, sizeof(lastime));

			delta = (difference.tv_sec * 1000000) + difference.tv_usec;
			elapsed += delta;
		}

		render(renderersurface, delta, elapsed);

		int sleep = FRAMETIME - delta;
		if (sleep > 0) {
			usleep(sleep);
		}

		SDL_Event event;
		while (SDL_PollEvent(&event)) {
			if (event.type == SDL_QUIT) {
				printf("got sdl quit, exiting\n");
				state = EXIT;
			}
		}
	}

	g_thread_join(pthread);
	disposerenderers();

	SDL_FreeSurface(renderersurface);

	TTF_CloseFont(font);
	TTF_Quit();
	SDL_Quit();

}

